/*
 * The MIT License
 * 
 * Copyright (c) 2008 Comcast Interactive Media
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package cim.sf.core.command.sh;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author <a href="mailto:jeremy_volkman@cable.comcast.com">Jeremy Volkman</a>
 */
public class CommandWriter {
    private String commandTemplate;
    private Activator activator;
    
    public CommandWriter(Activator activator) {
        this.activator = activator;
        if (activator.baseDirectory.exists()) {
            // Clean out old commands
            try {
                clearDirectory(activator.baseDirectory);
                if (!activator.baseDirectory.delete()) {
                    throw new RuntimeException("Could not delete base directory: " + activator.baseDirectory);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        File clientFile = new File(activator.baseDirectory, ".client.pl");
        clientFile.getParentFile().mkdirs();
        try {
            FileOutputStream fos = new FileOutputStream(clientFile);
            InputStream fis = getClass().getResourceAsStream("client.pl");
            copyStream(fis, fos, true);
            setClientPermissions(clientFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        
        File keyFile = new File(activator.baseDirectory, ".key");
        FileWriter fw = null;
        try {
            fw = new FileWriter(keyFile);
            fw.write(activator.key);
            fw.close();
            setKeyPermissions(keyFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try { fw.close(); } catch (Exception e) {}
        }
        
        
        try {
            InputStream is = getClass().getResourceAsStream("command.sh");
            String template = toString(is, true);
            this.commandTemplate = template.replace("{CLIENT}",
                    clientFile.getAbsolutePath()).replace("{HOST}", "localhost")
                    .replace("{PORT}", Integer.toString(activator.port));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public void cleanUp() {
        File clientFile = new File(activator.baseDirectory, ".client.pl");
        File keyFile = new File(activator.baseDirectory, ".key");
        clientFile.delete();
        keyFile.delete();
    }

    public void writeCommand(String path) {
        File commandFile = getCommandFile(path);
        commandFile.getParentFile().mkdirs();
        String commandText = commandTemplate.replace("{CMD}", path);
        try {
            FileWriter w = new FileWriter(commandFile);
            w.write(commandText);
            w.close();
            makeExecutable(commandFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private void makeExecutable(File file) throws IOException {
        String mode = "7" + 
            (activator.groupAllowed ? '5' : '0') + 
            (activator.othersAllowed ? '5' : '0');
        chmod(file, mode);
    }

    private void setClientPermissions(File clientFile) throws IOException {
        makeExecutable(clientFile);
    }
    
    private void setKeyPermissions(File keyFile) throws IOException {
        String mode = "6" +
            (activator.groupAllowed ? '4' : '0') + 
            (activator.othersAllowed ? '4' : '0');
        chmod(keyFile, mode);
    }
    
    private void chmod(File file, String mode) throws IOException {
        Runtime.getRuntime().exec(
                new String[] { "/bin/chmod", mode, file.getPath() });
    }

    public void removeCommand(String path) {
        // Each CommandGroup much have a unique path, so we can assume that all
        // files under this CommandGroup's directory can be removed (but not
        // directories)
        File commandFile = getCommandFile(path);
        File commandDir = commandFile.getParentFile();
        commandFile.delete();
        removeEmptyDirectoryRecursive(commandDir);
    }

    private File getCommandFile(String path) {
        char replaceSep = File.separatorChar == '\\' ? '/' : '\\';
        return new File(activator.baseDirectory, path.replace(replaceSep, File.separatorChar));
    }

    private void removeEmptyDirectoryRecursive(File directory) {
        try {
            if (activator.baseDirectory.getCanonicalPath().startsWith(
                    directory.getCanonicalPath())) {
                return; // given directory is equal to or above base directory
            }
        } catch (IOException e) {
            return;
        }
        String[] files = directory.list();
        if (files == null) {
            return; // not a directory?
        }
        if (files.length == 0) {
            //Empty directory
            File parent = directory.getParentFile();
            directory.delete();
            removeEmptyDirectoryRecursive(parent);
        }
    }

    private static void clearDirectory(File dir) throws IOException {
        IOException ex = null;

        if (!dir.isDirectory()) {
            throw new IOException("Not a directory: " + dir);
        }

        File[] files = dir.listFiles();
        if (files == null) {
            throw new IOException("Could not list contents of directory: " + dir);
        }

        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            try {
                if (file.isDirectory()) {
                    clearDirectory(file);
                    if (!file.delete()) {
                        ex = new IOException("Could not delete directory (perhaps not empty?): " + file);
                    }
                } else {
                    if (!file.delete()) {
                        ex = new IOException("Could not delete file: " + file);
                    }
                }
            } catch (IOException e) {
                ex = e;
            }
        }

        if (ex != null) {
            throw ex;
        }
    }
    
    public static int copyStream(InputStream in, OutputStream out, boolean closeStreams) throws IOException {
        byte[] buf = new byte[8192];
        int len = 0;
        int copied = 0;
        try {
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
                copied += len;
            }
        } finally {
            if (closeStreams) {
                try { in.close(); } catch (IOException e) {}
                try { out.close();} catch (IOException e) {}
            }
        }
        return copied;
    }
    
    public static String toString(InputStream in, boolean closeStream) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        copyStream(in, out, closeStream);
        return out.toString();
    }
}
